package top.fullj.actor;

/**
 * ActorSystem contains all hierarchical structured Actors
 *
 * @author wurenhai
 * @since 2018/3/11
 */
public class ActorSystem {

    /**
     * create ActorSystem instance
     *
     * @param name name of the ActorSystem
     */
    public static ActorSystem create(String name) {
        return new ActorSystem(name);
    }

    /**
     * user Actor context: /usr
     */
    public final ActorContext usr;

    /**
     * sys Actor context: /sys
     */
    public final ActorContext sys;

    /**
     * Dead letters actor: /sys/dlq
     */
    public final ActorRef deadLetters;

    /**
     * ActorSystem name
     */
    public final String name;

    private final long startTime;

    public long startTime() {
        return startTime;
    }

    /**
     * run time since create(ms)
     */
    public long upTime() {
        return System.currentTimeMillis() - startTime;
    }

    /**
     * create new child usr Actor
     */
    public ActorRef actorOf(Class<? extends Actor> type, Props props) {
        return usr.actorOf(type, props);
    }

    /**
     * create new child usr Actor with specified name
     */
    public ActorRef actorOf(Class<? extends Actor> type, Props props, String name) {
        return usr.actorOf(type, props, name);
    }

    public ActorSelection select(String path) {
        return usr.select(true, path);
    }

    public void shutdown() {
        usr.stop();
        sys.stop();
    }

    /**
     * stop Actor
     */
    public void stop(ActorRef actor) {
        usr.stop(actor);
    }

    private ActorSystem(String name) {
        //create root(/) actor
        Actor root = new RootGuardian();
        ActorContext rootContext = new ActorContext(this,
                RootGuardian.class,
                root,
                ActorPath.ROOT,
                Props.with(this),
                ActorRef.NONE,
                new ActorPath(ActorPath.SYSTEM));

        //create user(/usr) actor
        Actor usrGuardian = new UserGuardian();
        ActorContext usrContext = new ActorContext(this,
                UserGuardian.class,
                usrGuardian,
                ActorPath.USR,
                Props.with(this),
                rootContext.self,
                rootContext.path);

        //create system(/sys)
        Actor sysGuardian = new SystemGuardian();
        ActorContext sysContext = new ActorContext(this,
                SystemGuardian.class,
                sysGuardian,
                ActorPath.SYS,
                Props.with(this),
                rootContext.self,
                rootContext.path);

        this.usr = usrContext;
        this.sys = sysContext;
        this.deadLetters = this.sys.actorOf(DeadLetters.class,
                Props.with(this),
                ActorPath.DEAD_LETTERS);
        this.name = name;
        this.startTime = System.currentTimeMillis();
    }

    @SuppressWarnings("unused")
    protected void onDeadMessage(ActorRef sender, Object message) {

    }

    @SuppressWarnings("unused")
    protected void onRootMessage(ActorRef sender, Object message) {

    }

    @SuppressWarnings("unused")
    protected void onUserMessage(ActorRef sender, Object message) {

    }

    @SuppressWarnings("unused")
    protected void onSysMessage(ActorRef sender, Object message) {

    }

    /**
     * dead message Actor(/dlq)
     */
    class DeadLetters extends Actor {
        @Override
        public void onReceive(ActorRef sender, Object message) {
            onDeadMessage(sender, message);
        }
    }

    /**
     * user Actor(/usr)
     */
    class UserGuardian extends Actor {
        @Override
        public void onReceive(ActorRef sender, Object message) {
            onUserMessage(sender, message);
        }
    }

    /**
     * root Actor(/)
     */
    class RootGuardian extends Actor {
        @Override
        public void onReceive(ActorRef sender, Object message) {
            onRootMessage(sender, message);
        }
    }

    /**
     * system Actor(/sys)
     */
    class SystemGuardian extends Actor {
        @Override
        public void onReceive(ActorRef sender, Object message) {
            onSysMessage(sender, message);
        }
    }

}
